#include "net_bp.h"
#include "node_bp.h"

#include <stdlib.h>
#include <fstream>
#include <math.h>

namespace ann
{

net_bp::net_bp(): net()
{
    //hacking for the default struct
    m_lStruct.push_back(2);
    m_lStruct.push_back(2);
    m_lStruct.push_back(1);

    Init();
}

net_bp::net_bp(const uVec& pLStruct): net()
{
    m_lStruct = pLStruct;

    Init();
}

/*virtual*/ net_bp::~net_bp(void)
{
}

const UINT net_bp::Init(const wchar_t* pInitFile /*= NULL*/)
{
    assert( m_lStruct.size() > 1 );
    
    m_inCnt  = *m_lStruct.begin();
    m_outCnt = *m_lStruct.rbegin();

    for( uVec::iterator itr = m_lStruct.begin() + 1; itr != m_lStruct.end(); ++itr )
    {
        UINT lCnt = *(itr-1);
        for( UINT i = 0; i < (*itr); ++i )
        {
            node* bpNode = new node_bp(lCnt, &Tansig);
            m_nodes.push_back(bpNode);
        }
    }

    InitFromFile(L"bp_xor_init.info");
    //TODO: variaty init methods

    m_status |= eann_Initialized;
    return m_status;
}

const UINT net_bp::InitFromFile(const wchar_t* pInitFile)
{
    assert( pInitFile != NULL );

    std::ifstream inf( pInitFile, std::ifstream::in );
    if ( !inf.is_open() )
    {
        DEBUG("error: can't open init file.");
        m_status |= eann_FOINITFile;
        return m_status;
    }

    UINT cnt = 0;
    for( UINT i = 0; i < m_lStruct.size(); ++i )
    {
        inf>>cnt;
        if ( cnt!=m_lStruct[i] )
        {
            DEBUG("error: init file error, net struct mismatch.");
            m_status |= eann_FEINITFile;
            return m_status;
        }
    }

    double tVal = 0.0;
    for( npVec::iterator itr = m_nodes.begin(); itr != m_nodes.end(); ++itr )
    {
        for( UINT i = 0; i < (*itr)->Get_m_inCnt(); ++i )
        {
            inf>>tVal;
            dynamic_cast<node_bp*>(*itr)->Set_m_weight(tVal, i);
        }
        inf>>tVal;
        (*itr)->Set_m_b(tVal);
    }

    return m_status;
}

const UINT net_bp::Reset()
{
    //TODO: reset to init status

    m_status = 0;

    return m_status;
}

const UINT net_bp::Sim(const dVec& pInVec, dVec* pOutVec /*= NULL*/)
{
    assert( pInVec.size() == m_inCnt );
    //TODO: check input value boundry

    UINT maxCnt = *std::max_element(m_lStruct.begin(), m_lStruct.end());
    double* inVec = new double[maxCnt];
    double* outVec= new double[maxCnt];
    UINT inCnt = maxCnt;
    std::fill(inVec, inVec+maxCnt, 0.0);
    std::fill(outVec, outVec+maxCnt, 0.0);

    std::copy(pInVec.begin(), pInVec.end(), inVec);
    inCnt = pInVec.size();
    
    for( UINT i = 1; i < m_lStruct.size(); ++i )
    {
        for( UINT j = 0; j < m_lStruct[i]; ++j )
        {
            node& curNode = *m_nodes[GetNodeIdx(i,j)];
            outVec[j] = curNode.tick(inVec, inCnt);
        }
        std::copy(outVec, outVec+m_lStruct[i], inVec);
        inCnt = m_lStruct[i];
    }

    if (pOutVec == NULL)
    {
        return m_status;
    }
    
    pOutVec->clear();
    for( UINT i = 0; i < inCnt; ++i )
    {
        pOutVec->push_back(inVec[i]);
    }

    SAFE_DELA(inVec)
    SAFE_DELA(outVec)
    return m_status;
}

const UINT net_bp::Train()
{
    UINT counter = 0;

    for( ; !(m_status&eann_Converged); )
    {
        ++counter;
        for( trVec::iterator itr = m_trData.begin(); itr != m_trData.end(); ++itr )
        {
            TRData& curTRData = **itr;
            this->Sim(curTRData.m_inVec);
            curTRData.m_ek = this->CalEk(curTRData.m_outVec);
            this->PropogateBack(curTRData.m_outVec, curTRData.m_inVec);
        }
        this->TestConverge();
    }
    
    if ( this->IsConverged() )
    {
        printf("%s: BP net converged after %d iteration\n", __FUNCTION__, counter);
    }

    return m_status;
}

const UINT net_bp::PropogateBack( const dVec& pObjVec, const dVec& pInVec )
{
    assert( pObjVec.size() == m_outCnt );
    
    UINT maxCnt = *std::max_element(m_lStruct.begin(), m_lStruct.end());
    double* lDelta = new double[maxCnt];
    double* nDelta = new double[maxCnt];
    std::fill(lDelta, lDelta+maxCnt, 0.0);
    std::fill(nDelta, nDelta+maxCnt, 0.0);

    UINT ll = m_lStruct.size() - 1;
    UINT lCnt = m_lStruct[ll];
    double tSum = 0.0;
    double tVal = 0.0;

    //last layer
    for( UINT i = 0; i < lCnt; ++i )
    {
        node& curNode = *(m_nodes[GetNodeIdx(ll,i)]);
        tVal = curNode.Get_m_data();
        lDelta[i] = 2.0 * (pObjVec[i] - tVal) * tVal * (1.0 - tVal);

        curNode.UpdateW(m_alpha, lDelta[i]);
    }
    

    //follow layer
    for( --ll; ll > 0; --ll )
    {
        for( UINT i = 0; i < m_lStruct[ll]; ++i)
        {
            tSum = 0.0;
            node& curNode = *(m_nodes[GetNodeIdx(ll,i)]);
            tVal = curNode.Get_m_data();
            for( UINT j = 0; j < lCnt; ++j )
            {
                double lstWet = dynamic_cast<node_bp*>(m_nodes[GetNodeIdx(ll+1,j)])->Get_m_weight(i);
                tSum += lDelta[j]*lstWet;
            }
            nDelta[i] = tSum * tVal * (1.0 - tVal);

            curNode.UpdateW(m_alpha, nDelta[i]);
        }
        lCnt = m_lStruct[ll];
        std::copy(nDelta, nDelta+maxCnt, lDelta);
    }


    SAFE_DELA(lDelta)
    SAFE_DELA(nDelta)
    return m_status;
}

const UINT net_bp::TestConverge()
{
    double tVal = 0.0;

    for( trVec::iterator itr = m_trData.begin(); itr != m_trData.end(); ++itr )
    {
        tVal += (*itr)->m_ek;
    }
    tVal /= static_cast<double>(m_trData.size());
    tVal = sqrt(tVal);
    
    if ( tVal < m_criEk)
    {
        m_status |= eann_Converged;
    }

    DEBUG("%s: reached %2.8f\n", __FUNCTION__, tVal);
    
    return m_status;
}

}
